#pragma once

#include "MatrixType.h"
#include "IComplexMatrix.h"
#include "ComplexGeneralMatrix.h"
#include "ComplexGeneralTridiagonalMatrix.h"
#include "ComplexGeneralBandMatrix.h"
#include "ComplexHermitianMatrix.h"
#include "ComplexHermitianTridiagonalMatrix.h"
#include "ComplexHermitianBandMatrix.h"

namespace LatoolNet {

	using namespace System;
	using namespace System::IO;
	using namespace System::Collections::Generic;
	using namespace System::Text;

	///
	///<summary> 
	/// This class stands for a matrix of complex type.
	/// </summary>
	///
	public ref class ComplexMatrix {
	private:

		IComplexMatrix ^ m_mat;

		ComplexMatrix(IComplexMatrix ^ imat) {
			m_mat = imat;
		};

	internal:

		IComplexMatrix ^ GetInternalMatrix() {
			return m_mat;
		};

	protected:

		!ComplexMatrix() {
			delete m_mat;

		};

	public:

		~ComplexMatrix() {
			this->!ComplexMatrix();
		};

		ComplexMatrix(int rownum, int colnum) {
			m_mat = gcnew ComplexGeneralMatrix(rownum, colnum);
		};

		ComplexMatrix(int rownum, int colnum, int subnum, int supernum) {
			m_mat = gcnew ComplexGeneralBandMatrix(rownum, colnum, subnum, supernum);
		};

		ComplexMatrix(int rownum, int colnum, MatrixType type, int subnum, int supernum) {
			if (type == MatrixType::ComplexHermitianBand) {
				m_mat = gcnew ComplexHermitianBandMatrix(rownum, colnum, subnum, supernum);
			} else if (type == MatrixType::ComplexGeneralBand) {
				m_mat = gcnew ComplexGeneralBandMatrix(rownum, colnum, subnum, supernum);
			}
		};

		ComplexMatrix(int rownum, int colnum, int bandWidth) {
			if (bandWidth == 3) {
				m_mat = gcnew ComplexGeneralTridiagonalMatrix(rownum, colnum);
			} else if (bandWidth % 2 != 0) {
				int subnum = (bandWidth - 1) / 2;
				int supernum = (bandWidth - 1) / 2;
				m_mat = gcnew ComplexGeneralBandMatrix(rownum, colnum, subnum, supernum);
			} else {
				throw gcnew LatoolException("Please specify both super and sub diagonals.");
			}
		};

		ComplexMatrix(int rownum, int colnum, MatrixType type) {
			if (type == MatrixType::ComplexGeneral) {
				m_mat = gcnew ComplexGeneralMatrix(rownum, colnum);
			} else if (type == MatrixType::ComplexHermitian) { 
				m_mat = gcnew ComplexHermitianMatrix(rownum, colnum);
			} else if (type == MatrixType::ComplexHermitianTridiagonal) {
				m_mat = gcnew ComplexHermitianTridiagonalMatrix(rownum, colnum);
			}
		};


		///
		///<summary>
		/// Indexer for the matrix element. 
		/// </summary>
		///<summary>
		/// Factorized band matrix can not be fully accessible 
		/// because its memory allocation is changed during 
		/// the factorization by the lapack routine. 
		///</summary>
		///
		property Complex default[int, int] {
			Complex get(int i, int j) {
				Validator::CheckMatrixRange(m_mat, i, j);
				return m_mat[i, j];	
			}
			void set(int i, int j, Complex value) {
				Validator::CheckMatrixRange(m_mat, i, j);
				m_mat[i, j] = value;
			}
		};

		///
		///<summary>
		/// Returns the type of this matrix.
		///</summary>
		///
		property MatrixType Type {
			MatrixType get() { return m_mat->Type; }
		};

		///
		///<summary>
		/// Returns the number of rows in this matrix.
		///</summary>
		///
		property int RowNum {
			int get() { return m_mat->RowNum; }
		};

		///
		///<summary>
		/// Returns the number of columns in this matrix. 
		///</summary>
		///
		property int ColNum {
			int get() { return m_mat->ColNum; }
		};

		///
		///<summary>
		/// Returns true if matrix is factorized. 
		///</summary>
		///
		property bool IsFactorized {
			bool get() { return m_mat->IsFactorized; }
		};

		///
		///<summary>
		/// Returns an inverted matrix of this matrix. 
		/// The result is overwritten to this matrix. 
		/// </summary>
		/// <summary>
		///	Inversion is applicable only to general matrix. 
		/// </summary>
		///
		ComplexMatrix ^ Inv() {
			if (m_mat->IsFactorized == false) {
				m_mat->Factorize();		
			}
			m_mat->Invert();

			return this;
		};

		///
		///<summary>  
		/// Creates a copy of this matrix. 
		///</summary>
		///
		ComplexMatrix ^ Clone() {
			return gcnew ComplexMatrix(m_mat->Clone());
		};

		///
		///<summary>
		/// Computes production of matrices. 
		/// This operation always returns a new general matrix.
		///</summary>
		///
		static ComplexMatrix ^ operator *(ComplexMatrix ^ lhs, ComplexMatrix ^ rhs) {

			if (lhs->ColNum != rhs->RowNum) {
				throw gcnew LatoolException("Column size of left matrix must be equal to row size of right matrix.");
			}
			int rownum = lhs->RowNum;
			int colnum = rhs->ColNum;
			int n = lhs->ColNum;

			ComplexMatrix ^ ret = gcnew ComplexMatrix(rownum, colnum);
			for (int i = 0; i < rownum; i++) {
				for (int j = 0; j < colnum; j++) {
					ret[i, j] = Complex(0, 0);
					for (int k = 0; k < n; k++) { 
						ret[i, j] += lhs[i, k] * rhs[k, j]; 
					}
				}
			}
			return ret;
		};

		static ComplexMatrix^ operator *(Complex lhs, ComplexMatrix ^ rhs) {

			int rownum = rhs->RowNum;
			int colnum = rhs->ColNum;

			ComplexMatrix ^ ret = gcnew ComplexMatrix(rownum, colnum);
			for (int i = 0; i < rownum; i++) {
				for (int j = 0; j < colnum; j++) {
					ret[i, j] = lhs * rhs[i, j]; 
				}
			}
			return ret;
		}

		static ComplexMatrix^ operator *(double lhs, ComplexMatrix ^ rhs) {

			int rownum = rhs->RowNum;
			int colnum = rhs->ColNum;

			ComplexMatrix ^ ret = gcnew ComplexMatrix(rownum, colnum);
			for (int i = 0; i < rownum; i++) {
				for (int j = 0; j < colnum; j++) {
					ret[i, j] = lhs * rhs[i, j]; 
				}
			}
			return ret;
		}

		///
		///<summary>
		/// Returns a string representation of this matrix.
		///</summary>
		///
		virtual String^ ToString() new {
			return MatrixFormatter::FormatString(m_mat, gcnew MatrixFormatter());
		};

		///
		///<summary>
		/// Returns a string representation of this matrix with specified number of digit. 
		///</summary>
		///
		String^ ToString(int numDigit) {
			MatrixFormatter^ format = gcnew MatrixFormatter();
			format->NumberOfDigit = numDigit;
			return MatrixFormatter::FormatString(m_mat, format);
		};

		///
		///<summary>
		/// Returns a string representation of this matrix with specified format. 
		///</summary>
		///
		String^ ToString(MatrixFormatter^ format) {
			return MatrixFormatter::FormatString(m_mat, format);
		};

		///
		///<summary>
		/// Saves a matrix to a file. 
		///</summary>
		///<summary>
		/// Factorized flag is lost with this method. 
		/// Also, factorized general band matrix can not be propery saved.
		///</summary>
		///
		static void Save(ComplexMatrix ^ mat, String^ outfile) {
			StreamWriter ^ writer = File::CreateText(outfile);
			writer->WriteLine(mat->ToString());
			writer->Close();			
		};

		///
		///<summary>
		/// Saves a matrix to a file with specified number of digit.
		///</summary>
		///<summary>
		/// Factorized flag is lost with this method. 
		/// Also, factorized general band matrix can not be propery saved.
		///</summary>
		///
		static void Save(ComplexMatrix ^ mat, int numDigit, String^ outfile) {
			StreamWriter ^ writer = File::CreateText(outfile);
			writer->WriteLine(mat->ToString(numDigit));
			writer->Close();			
		};

		///
		///<summary>
		/// Saves a matrix to a file with specified format. 
		///</summary>
		///<summary>
		/// Factorized flag is lost with this method. 
		/// Also, factorized general band matrix can not be propery saved.
		///</summary>
		///
		static void Save(ComplexMatrix^ mat, MatrixFormatter^ format, String^ outfile) {
			StreamWriter ^ writer = File::CreateText(outfile);
			writer->WriteLine(mat->ToString(format));
			writer->Close();			
		};

		///
		///<summary>
		/// Creates a matrix instance from the specified file. 
		///</summary>
		///<summary>
		/// Load method can not restore the factorized flag. 
		/// Also, Factorized general band matrix can not be propery loaded.
		///</summary>
		///
		static ComplexMatrix^ Load(String^ loadFile) {
			return Load(loadFile, gcnew MatrixFormatter());
		};

		///
		///<summary>
		/// Creates a matrix instance from the specified file. 
		/// (Not fully tested. It is recommended to use simple string patterns.) 
		///</summary>
		///<summary>
		/// Load method can not restore the factorized flag. 
		/// Also, factorized general band matrix can not be propery loaded.
		///</summary>
		///
		static ComplexMatrix ^ Load(String^ loadFile, MatrixFormatter^ format) {
			StreamReader ^ reader = File::OpenText(loadFile);

			List<array<Complex>^>^ list = gcnew List<array<Complex>^>();
			while (reader->Peek() != -1) {
				String^ line = reader->ReadLine();

				if (line->Trim()->Equals("")) {
					continue;
				}
				list->Add(MatrixFormatter::ParseComplexLine(line, format));
			}
			reader->Close();

			int rownum = list->Count;
			int colnum = 0;
			for (int i = 0; i < rownum; i++) {
				int tokensize = list[i]->Length;
				if (colnum < tokensize) {
					colnum = tokensize;
				}
			}

			ComplexMatrix ^ ret = gcnew ComplexMatrix(rownum, colnum);
			for (int i = 0; i < rownum; i++) {
				array<Complex>^ tokens = list[i];

				for (int j = 0; j < tokens->Length; j++) {
					ret[i, j] = tokens[j];
				}

			}
			return ret;
		};

		static ComplexMatrix^ RowVector(ComplexMatrix^ mat, int rownum) {
			ComplexMatrix^ ret = gcnew ComplexMatrix(1, mat->ColNum);
			for (int i = 0; i < mat->ColNum; i++) {
				ret[0, i] = mat[rownum, i];
			}
			return ret;
		};

		static ComplexMatrix^ ColVector(ComplexMatrix^ mat, int colnum) {
			ComplexMatrix^ ret = gcnew ComplexMatrix(mat->RowNum, 1);
			for (int i = 0; i < mat->RowNum; i++) {
				ret[i, 0] = mat[i, colnum];
			}
			return ret;
		};

	};
}